﻿using System;
using System.Collections.Generic;
using Interop = System.Runtime.InteropServices;
using System.Windows.Forms;
using NetOffice;
using Word = NetOffice.WordApi;
using NetOffice.WordApi.Enums;
using Office = NetOffice.OfficeApi;
using NetOffice.OfficeApi.Enums;
using VBIDE = NetOffice.VBIDEApi;
using NetOffice.VBIDEApi.Enums;
using NetOffice.Tools;
using NetOffice.WordApi.Tools;
using SimonPG;

// note: Word Addinを作成する際に便利なフレームワークを提供

namespace SimonPG.Office {

    // sec: Addin helper core

    [Interop.ComVisible(false)]
    public class WordAddinHelper<T_Addin, T_Config, T_Info> 
        where T_Addin : NetOffice.WordApi.Tools.COMAddin
        where T_Config : ConfigAsJsonBase<T_Config>, new()
        where T_Info : class {
        // assume: COM公開されたAddinクラスで本クラスのインスタンスを持つように
        // assume: OnNeedToCreateCtpByDocにCustomTaskPaneの生成／設定ロード処理を実装
        // assume: OnNeedToSaveCtpWhenEndにCustomTaskPaneの設定保存処理を実装
        // assume: Configクラスを別途作成、設定をこれに格納するように

        public T_Addin Addin;
        Func<Word.Application> _AppGetter;
        Func<T_Config> _ConfigLoader;
        public Word.Application WordApp;
        public T_Config Config;
        public InfoByDocManager<T_Info> InfoByDoc;

        public event Action<Word.Window, InfoByDocManager<T_Info>.Set> OnNeedToCreateCtpByDoc;
        public event Action<Word.Window, InfoByDocManager<T_Info>.Set> OnNeedToSaveCtpWhenEnd;

        // sec: core process

        public WordAddinHelper(T_Addin addin, 
            Func<Word.Application> word_app_getter, Func<T_Config> config_loader) {
            // above: app_getter, config_loader: 言語仕様でアクセスできないため止むを得ずアクセサを追加

            this.Addin = addin;
            _AppGetter = word_app_getter;
            _ConfigLoader = config_loader;

            // sec: 例外処理
#if !DEBUG
            System.Windows.Forms.Application.ThreadException += _Application_ThreadException;
            System.Threading.Thread.GetDomain().UnhandledException += _Program_UnhandledException;
            // above: Wordから始まる動作での例外はキャッチされていない
#endif

            // sec: addin events

            this.Addin.OnStartupComplete += new OnStartupCompleteEventHandler(Addin_OnStartupComplete);
            this.Addin.OnConnection += new OnConnectionEventHandler(Addin_OnConnection);
            this.Addin.OnDisconnection += new OnDisconnectionEventHandler(Addin_OnDisconnection);

            // sec: init application

            System.Windows.Forms.Application.EnableVisualStyles();
        }

        #region Exception Handling

        static void _Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) {
            _ShowExceptionForm(e.Exception as Exception);
        }

        static void _Program_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
            _ShowExceptionForm(e.ExceptionObject as Exception);
        }

        static void _ShowExceptionForm(Exception ex) {

            var form = new SimonPG.ExceptionForm(ex);
            if (System.Windows.Forms.Application.OpenForms.Count > 0) {
                form.ShowDialog(System.Windows.Forms.Application.OpenForms[0]);
            } else {
                form.ShowDialog();
            }
        }

        #endregion

        #region Events from Addin

        void Addin_OnConnection(object Application, NetOffice.Tools.ext_ConnectMode ConnectMode, object AddInInst, ref Array custom) {

        }

        void Addin_OnStartupComplete(ref Array custom) {
            CreateUserInterface();
        }

        void Addin_OnDisconnection(NetOffice.Tools.ext_DisconnectMode RemoveMode, ref Array custom) {
            RemoveUserInterface();
        }

        #endregion

        // sec: CustomTaskPane

        #region Classic UI

        void CreateUserInterface() {

            this.WordApp = _AppGetter(); // ここで初めてWordのApplicationオブジェクトにアクセス可
            this.Config = _ConfigLoader();

            // sec: init InfoByDocManager

            this.InfoByDoc = new InfoByDocManager<T_Info>(this.WordApp);
            this.InfoByDoc.OnCreated += _InfoByDoc_OnCreated;
            this.InfoByDoc.OnDelete += _InfoByDoc_OnDelete;
            this.InfoByDoc.OnStartupComplete();
        }

        void _InfoByDoc_OnCreated(Word.Window window, InfoByDocManager<T_Info>.Set set) {

            if (this.OnNeedToCreateCtpByDoc != null) {
                this.OnNeedToCreateCtpByDoc(window, set);
            }
        }

        void _InfoByDoc_OnDelete(Word.Window window, InfoByDocManager<T_Info>.Set set) {

            if (this.InfoByDoc.GetInfoCount() > 0) // if: 最後のWordウィンドウが閉じる場合以外
                return;

            if (this.OnNeedToSaveCtpWhenEnd != null) {
                this.OnNeedToSaveCtpWhenEnd(window, set);
            }
        }

        void RemoveUserInterface() { }

        #endregion
    }

    // sec: config

    /*
    [Interop.ComVisible(false)]
    public class Config : ConfigAsJsonBase<Config> {
        // assume: 設定格納クラスの例

        // sec: 設定項目

        public bool Visible = true;
    }
    */

    // sec: feature classes

    [Interop.ComVisible(false)]
    public class InfoByDocManager<T> where T : class {
        // note: Word.Documentごとに情報を自動で管理する

        Word.Application _WordApp;
        List<Set> _InfoByDoc = new List<Set>();

        public event Action<Word.Window, Set> OnCreated;
        public event Action<Word.Window, Set> OnDelete;

        public InfoByDocManager(Word.Application word_app) {
            // assume: Addin_OnStartupCompleteのイベントタイミングで生成すること

            _WordApp = word_app;
            _WordApp.NewDocumentEvent += _WordApp_DocumentOpenEvent;
            _WordApp.DocumentOpenEvent += _WordApp_DocumentOpenEvent;
            _WordApp.DocumentBeforeCloseEvent += _WordApp_DocumentBeforeCloseEvent;
        }

        public void OnStartupComplete() {
            // assume: 本クラス生成、初期設定後、すぐに呼び出すこと

            _WordApp_DocumentOpenEvent(_WordApp.ActiveDocument); // Word起動時の「開く」に対応
        }

        void _WordApp_DocumentOpenEvent(Word.Document doc) {

            if (this.GetInfo(doc.ActiveWindow) != null) // if: すでに存在するWindow
                return;

            var set = new Set() {
                Window = doc.ActiveWindow,
                Info = null
            };
            _InfoByDoc.Add(set);

            if (this.OnCreated != null)
                this.OnCreated(doc.ActiveWindow, set);
        }

        void _WordApp_DocumentBeforeCloseEvent(Word.Document doc, ref bool cancel) {

            Set set;
            for (var i = _InfoByDoc.Count - 1; i >= 0; i--) {

                if (_InfoByDoc[i].Window == doc.ActiveWindow) {
                    set = _InfoByDoc[i];
                    _InfoByDoc.RemoveAt(i);
                    goto FOR_END;
                }
            }
            return; // if: すでに存在しないWindow
        FOR_END:

            if (this.OnDelete != null)
                this.OnDelete(doc.ActiveWindow, set);
        }

        public T GetInfo(Word.Window active_window) {

            foreach (var item in _InfoByDoc) {

                if (item.Window == active_window)
                    return item.Info;
            }

            return null;
        }

        public int GetInfoCount() {
            return _InfoByDoc.Count;
        }

        [Interop.ComVisible(false)]
        public class Set {
            public Word.Window Window;
            public T Info;
        }
    }
}
